package cn.t.keycloak.demo.mfa.sms.action;

import cn.hutool.core.lang.Validator;
import cn.t.keycloak.demo.jpa.VerifyPhoneEntity;
import cn.t.keycloak.demo.mfa.sms.SmsProperties;
import cn.t.keycloak.demo.mfa.sms.credential.SmsCredentialModel;
import cn.t.keycloak.demo.mfa.sms.credential.SmsCredentialProvider;
import cn.t.keycloak.demo.mfa.sms.credential.SmsCredentialProviderFactory;
import cn.t.keycloak.demo.mfa.sms.util.SmsCache;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils;

import java.util.Date;
import java.util.Objects;

/**
 * 定义一个用户注册后的必须操作
 * @author 陶敏麒
 * @date 2023/11/16 10:57
 */
@Slf4j
public class SmsOptMfaAuthRequiredAction implements RequiredActionProvider {

    public static final String PROVIDER_ID = "sms_phone_config";
    private static final String ERROR_MSG = "errorMsg";
    /**
     * 这里定义什么时候触发
     */
    @Override
    public void evaluateTriggers(RequiredActionContext context) {

        // 是否已经配置过凭证了
        var smsCredential = SmsCredentialModel.queryCredentialModelByUser(context.getUser());

        if (smsCredential.isEmpty()) {
            log.warn("--->当前用户[{}]还未配置手机", context.getUser().getUsername());
            context.getUser().addRequiredAction(PROVIDER_ID);
        } else {
            context.getUser().removeRequiredAction(PROVIDER_ID);
        }
        // 后期可以改为校验用户是否添加过凭证
        // 用户没有绑定过手机号码则弹出绑定
        // if (!context.getUser().getAttributes().containsKey("phoneNumber")) {
            // context.getUser().addRequiredAction(PROVIDER_ID);
        // }
    }

    /**
     * 定义一个用户配置时需要显示的表单
     */
    @Override
    public void requiredActionChallenge(RequiredActionContext requiredActionContext) {
        Response challenge = requiredActionContext.form().createForm(SmsProperties.SMS_CONFIG_FORM_NAME);
        requiredActionContext.challenge(challenge);
    }

    @Override
    public void processAction(RequiredActionContext requiredActionContext) {
        // 获取用户配置的手机号
        String phoneNumber = requiredActionContext.getHttpRequest().getDecodedFormParameters().getFirst("phoneNumber");
        // 获取验证码
        String code = requiredActionContext.getHttpRequest().getDecodedFormParameters().getFirst("validateCode");
        if (!Validator.isMobile(phoneNumber)) {
            log.warn("用户[{}]输入的手机号[{}]不合法", requiredActionContext.getUser().getUsername(),phoneNumber);
            // 校验手机号
            this.createFailureFormChallenge(requiredActionContext, new FormMessage(ERROR_MSG, "手机号码不合法"));
            return;
        }
        if (code == null || code.isBlank()) {
            this.createFailureFormChallenge(requiredActionContext, new FormMessage(ERROR_MSG, "验证码不能为空"));
            return;
        }

        // 很奇怪,直接用SmsCredentialProvider.class会获取不到
        SmsCredentialProvider provider = (SmsCredentialProvider) requiredActionContext.getSession().getProvider(CredentialProvider.class, SmsCredentialProviderFactory.PROVIDER_ID);

        if (provider == null) {
            this.createFailureFormChallenge(requiredActionContext, new FormMessage(ERROR_MSG, "验证码服务提供者为空"));
            return;
        }

        // 获取验证码
        String cacheCode = SmsCache.getCode(requiredActionContext.getRealm().getName(), phoneNumber);
        if (!Objects.equals(cacheCode, code)) {
            this.createFailureFormChallenge(requiredActionContext, new FormMessage(ERROR_MSG, "验证码不正确"));
            return;
        }
        // 清除缓存
        SmsCache.cleanCode(requiredActionContext.getRealm().getName(), phoneNumber);

        log.info(">>>>>用户[{}]手机号码验证成功", requiredActionContext.getUser().getUsername());
        provider.createCredential(requiredActionContext.getRealm(), requiredActionContext.getUser(), SmsCredentialModel.create(phoneNumber, ""));
        // 移除必须操作,下次这个用户不用再次验证了
        requiredActionContext.getUser().removeRequiredAction(PROVIDER_ID);
        // 给用加个手机号码属性
        requiredActionContext.getUser().setSingleAttribute("phoneNumber", phoneNumber);
        requiredActionContext.getUser().setSingleAttribute("phoneNumberVerified", "true");
        // 持久化
        if (!this.saveVerifiedPhone(requiredActionContext, phoneNumber)) {
            this.createFailureFormChallenge(requiredActionContext, new FormMessage(ERROR_MSG, "手机号已经被绑定"));
            return;
        }
        // throw new BadRequestException("aa");
        requiredActionContext.success();
    }

    /**
     * 持久化认证手机
     */
    private boolean saveVerifiedPhone(RequiredActionContext context, String phoneNumber) {
        // 设置用户已认证的手机
        var em = context.getSession().getProvider(JpaConnectionProvider.class).getEntityManager();
        if (!em.isJoinedToTransaction()) {
            log.warn("当前操作未加入事务,准备加入当前事务");
            em.joinTransaction();
        }
        // 先查询是否存在
        var duplicate = em.createNativeQuery("SELECT 1 FROM VERIFY_PHONE_LOG WHERE REALM_ID = ?1 AND PHONE_NUMBER = ?2");
        duplicate.setParameter(1, context.getRealm().getId());
        duplicate.setParameter(2, phoneNumber);
        if (duplicate.getResultList() != null && !duplicate.getResultList().isEmpty()) {
            log.warn("手机[{}]已经被认证了", phoneNumber);
            return false;
        }

        // 22.0.x的em管理器似乎不能正确定位到自定义表的实体,这里使用自定义sql
        var q = em.createNativeQuery("INSERT INTO VERIFY_PHONE_LOG(ID, REALM_ID,PHONE_NUMBER,CREATED_AT,USER_ID, CONFIRMED) VALUES(?1,?2,?3,?4,?5,?6)");
        q.setParameter(1, KeycloakModelUtils.generateId());
        q.setParameter(2, context.getRealm().getId());
        q.setParameter(3, phoneNumber);
        q.setParameter(4, new Date());
        q.setParameter(5, context.getUser().getId());
        q.setParameter(6, 1);
        int result = q.executeUpdate();
        if (result == 0) {
            log.warn("更新用户[{}]认证手机失败", context.getUser().getUsername());
            return false;
        }
        return true;

//        var user = context.getUser();
//        VerifyPhoneEntity phone = new VerifyPhoneEntity();
//        phone.setPhoneNumber(phoneNumber);
//        phone.setUserId(user.getId());
//        phone.setConfirmed(true);
//        phone.setRealmId(context.getRealm().getId());
//        em.persist(phone);
    }

    /**
     * 创建一个表单响应
     */
    private void createFailureFormChallenge(RequiredActionContext context, FormMessage errorMessage) {
        // 其实使用的都是同一个LoginForm
        LoginFormsProvider loginFormsProvider = context.form();
        if (Objects.nonNull(errorMessage)) {
            loginFormsProvider = loginFormsProvider.addError(errorMessage);
        }
        loginFormsProvider.setAttribute("phoneNumber", context.getHttpRequest().getDecodedFormParameters().getFirst("phoneNumber"));
        Response challenge = loginFormsProvider
                .createForm(SmsProperties.SMS_CONFIG_FORM_NAME);
        context.challenge(challenge);
    }

    @Override
    public void close() {}
}
